package main

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/hypebeast/go-osc/osc"
)

func decode_to_string(data []byte) (string, error) {
	var idx int
	var rult string
	var i32temp int32
	var f32temp float32

	if len(data) < 8 {
		return rult, errors.New(fmt.Sprintf("Recv : small data, %d", len(data)))
	}

	if data[0] != '/' {
		return rult, errors.New(fmt.Sprintf("Recv : path invalid fmt"))
	}

	// get string.
	idx = bytes.IndexByte(data, 0)
	if idx < 0 {
		return rult, errors.New(fmt.Sprintf("Recv : path invalid fmt"))
	}

	rult = string(data[0:idx])
	idx = (idx + 4) - (idx % 4)

	// tags
	if idx > len(data) {
		return rult, nil
	}

	data = data[idx:]
	idx = bytes.IndexByte(data, 0)
	if idx <= 1 {
		return rult, errors.New(fmt.Sprintf("Recv : tags invalid fmt"))
	}

	if data[0] != ',' {
		return rult, errors.New(fmt.Sprintf("Recv : tags invalid fmt"))
	}

	tags := data[1:idx]
	rult = rult + ", " + string(tags)
	idx = (idx + 4) - (idx % 4)

	// args
	if idx > len(data) {
		return rult, nil
	}

	buf := bytes.NewBuffer(data[idx:])
	for i := 0; i < len(tags); i++ {
		switch tags[i] {
		case 'i':
			if buf.Len() < 4 {
				return rult, errors.New(fmt.Sprintf("arg[%d] i32, data err", i))
			}
			binary.Read(buf, binary.BigEndian, &i32temp)
			rult = rult + ", " + fmt.Sprintf("%v", i32temp)

		case 'f':
			if buf.Len() < 4 {
				return rult, errors.New(fmt.Sprintf("arg[%d] f32, data err", i))
			}
			binary.Read(buf, binary.BigEndian, &f32temp)
			rult = rult + ", " + fmt.Sprintf("%v", f32temp)

		case 's':
			strtemp, err := buf.ReadString(0)
			if err != nil {
				return rult, errors.New(fmt.Sprintf("arg[%d] str, data err", i))
			}

			/* trim tail zero */
			rult = rult + ", " + strtemp[0:len(strtemp)-1]
			if (len(strtemp) % 4) != 0 {
				buf.Next(4 - (len(strtemp) % 4))
			}

		default:
			return rult, errors.New(fmt.Sprintf("arg[%d] type not support", i))
		}
	}

	return rult, nil
}

// ui << udp
func RecvThread(ui *TermUi, ucon *net.UDPConn) {
	var iret int
	// var radr *net.UDPAddr
	var err error
	var tary [4096]byte

	for {
		ucon.SetReadDeadline(time.Now().Add(100 * time.Second))
		iret, _, err = ucon.ReadFromUDP(tary[:])

		//
		if err != nil {

			// closed for goroutine wake.
			if errors.Is(err, net.ErrClosed) {
				ui.AddMessage(fmt.Sprintf("Recv : read udp : closed, go routine end."))
				return
			}

			fmt.Printf("Recv : read udp : %q\n", err)

		} else {
			// ui.AddMessage(fmt.Sprintf("Recv : return, %d, %v\r\n", iret, radr))
			//fmt.Printf("Recv : return data, %d\n", iret)
			sss, err := decode_to_string(tary[0:iret])
			if err != nil {
				ui.AddMessage("err: " + err.Error())
			} else {
				ui.AddMessage(sss)
			}

		}

	}

}

type RemoteConn struct {
	ui   *TermUi
	ucon *net.UDPConn
	pwd  string
}

func (rc *RemoteConn) TryDial(adr string) {

	if rc.ucon != nil {
		// close
		rc.ucon.Close()
		rc.ucon = nil
		rc.pwd = "/"
		//
		rc.ui.SetAddrInfo("----")
		rc.ui.SetPathInfo(rc.pwd)
	}

	radr, err := net.ResolveUDPAddr("udp4", adr)
	if err != nil {
		rc.ui.AddMessage(fmt.Sprintf("tryDial : addr, %q", err))
		return
	}

	ladr, err := net.ResolveUDPAddr("udp4", ":8000")
	if err != nil {
		rc.ui.AddMessage(fmt.Sprintf("tryDial : local addr, %q", err))
		return
	}

	ucon, err := net.DialUDP("udp4", ladr, radr)
	if err != nil {
		rc.ui.AddMessage(fmt.Sprintf("tryDial : %s", adr))
	}

	//
	rc.ui.SetAddrInfo(ucon.LocalAddr().String() + " >> " + ucon.RemoteAddr().String())
	rc.ui.SetPathInfo("/")

	//
	rc.ucon = ucon
	go RecvThread(rc.ui, ucon)
	return
}

func (rc *RemoteConn) ChangePath(dst string) error {

	if dst == ".." {
		if rc.pwd == "/" {
			return nil
		} else {
			rc.pwd = path.Dir(rc.pwd)
		}

	} else if strings.HasPrefix(dst, "/") {
		rc.pwd = path.Clean(dst)
	} else {
		rc.pwd = path.Join(rc.pwd, dst)
	}

	//
	rc.ui.SetPathInfo(rc.pwd)
	return nil
}

func (rc *RemoteConn) SendMsg(fds []string) error {
	var msg *osc.Message

	if strings.HasPrefix(fds[0], "/") {
		// absolute path
		msg = osc.NewMessage(fds[0])
	} else {
		// xiangdui path
		msg = osc.NewMessage(path.Join(rc.pwd, fds[0]))
	}

	//
	for i := 1; i < len(fds); i++ {

		// check, judge number or string
		val, err := strconv.ParseInt(fds[i], 0, 64)
		if err == nil {
			msg.Append(int32(val))
			continue
		}

		//
		msg.Append(fds[i])
	}

	//
	dat, err := msg.MarshalBinary()
	if err != nil {
		return errors.New("")
	}

	//
	rc.ucon.Write(dat)
	return nil
}

// ui >> udp
func SendThread(ui *TermUi) {
	var err error

	rc := &RemoteConn{ui, nil, "/"}
	rc.ui.SetPathInfo(rc.pwd)

	for {
		msg := <-ui.que

		// spit to array
		ary := strings.Fields(msg)
		if len(ary) == 0 {
			continue
		}

		// special system command..
		if strings.HasPrefix(ary[0], "#") {

			switch ary[0] {

			case "#udp":
				fmt.Printf("rt: con: %q\n", ary)
				if len(ary) < 2 {
					ui.AddMessage(fmt.Sprintf("%s : need udp addr.", ary[0]))
				} else {
					rc.TryDial(ary[1])
				}
				break

			case "#cd":
				if len(ary) < 2 {
					// nothing.
					ui.AddMessage(fmt.Sprintf("pwd, %v", rc.pwd))
				} else {
					err = rc.ChangePath(ary[1])
					if err != nil {
						ui.AddMessage(fmt.Sprintf("cd, %v", err.Error()))
					}
				}
				break

			case "#quit":
				if rc.ucon != nil {
					// close
					rc.ucon.Close()
					rc.ucon = nil
				}
				ui.PostQuit()
				break

			case "#help":
				ui.AddMessage("cmds : #udp ip:port")
				ui.AddMessage("cmds : #quit")
				break

			default:
				ui.AddMessage(fmt.Sprintf("%s : cmd not support.", ary[0]))
				break
			}

		} else if rc.ucon == nil {

			//
			ui.AddMessage("remote conn not ready")

		} else {
			err := rc.SendMsg(ary)
			if err != nil {
				ui.AddMessage(fmt.Sprintf("osc, send: %q", err))
			}
		}

	}
}
